package linc.fun.common.rest.http.service;

import cn.hutool.core.convert.Convert;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import linc.fun.common.core.exception.InternalException;
import linc.fun.common.rest.http.annotation.RestMethod;
import linc.fun.common.rest.http.annotation.RestMethodArg;
import linc.fun.common.rest.http.request.JsonRestRequest;
import linc.fun.common.rest.http.response.JsonRestResponse;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.reflections.Reflections;
import org.reflections.scanners.MethodAnnotationsScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author yqlin
 * @date 2022/5/5 13:00
 * @description
 */
@Slf4j
@Component
public class JsonRestService implements ApplicationContextAware {
    private final Map<String, Method> METHOD_CACHE;
    private final Map<String, Object> METHOD_INSTANCE_CACHE;
    private ApplicationContext context;

    public JsonRestService() {
        METHOD_CACHE = Maps.newConcurrentMap();
        METHOD_INSTANCE_CACHE = Maps.newConcurrentMap();
        this.initCache();
    }

    private void initCache() {
        log.info("load method....");
        ConfigurationBuilder configurationBuilder = new ConfigurationBuilder()
                .setUrls(ClasspathHelper.forPackage("linc.fun"))
                .setScanners(new MethodAnnotationsScanner());
        Stream.of(new Reflections(configurationBuilder).getMethodsAnnotatedWith(RestMethod.class)).forEach(methods -> {
            Map<String, Method> methodsMap = methods.stream().collect(Collectors.toMap(Method::getName, v -> v, (v1, v2) -> v1));
            METHOD_CACHE.putAll(methodsMap);
        });
        log.info("loaded {} methods....", METHOD_CACHE.size());
    }

    public JsonRestResponse<?> rest(JsonRestRequest request) {
        log.debug("执行线程: " + Thread.currentThread().getName());
        if (Objects.isNull(request) || Objects.isNull(request.getMethod()) || Objects.isNull(request.getParams())) {
            log.error("参数为空");
            return JsonRestResponse.buildFailure(JsonRestResponse.ErrorCode.MALFORMED, "参数为空");
        }
        Object result;
        final var methodName = request.getMethod();
        var method = METHOD_CACHE.get(methodName);
        if (Objects.isNull(method)) {
            return JsonRestResponse.buildFailure(JsonRestResponse.ErrorCode.METHOD_NOT_FOUND, "方法未找到");
        }
        var instance = METHOD_INSTANCE_CACHE.get(methodName);
        if (Objects.isNull(instance)) {
            var bean = context.getBean(method.getDeclaringClass());
            METHOD_INSTANCE_CACHE.put(methodName, bean);
            instance = bean;
        }
        try {
            result = method.invoke(instance, this.buildParams(request.getParams(), method).toArray());
        } catch (InvocationTargetException | IllegalAccessException e) {
            log.error("e :{}", e.getMessage(), e);
            return JsonRestResponse.buildFailure(JsonRestResponse.ErrorCode.INTER_ERROR, "内部错误");
        }
        return JsonRestResponse.of(result);
    }


    @SneakyThrows
    private List<Object> buildParams(Map<String, Object> paramMap, Method method) {
        List<@Nullable Object> params = Lists.newArrayListWithExpectedSize(paramMap.size());
        for (Parameter param : method.getParameters()) {
            RestMethodArg[] args = param.getDeclaredAnnotationsByType(RestMethodArg.class);
            if (paramMap.containsKey(param.getName())) {
                Object o = paramMap.get(param.getName());
                params.add(Convert.convert(param.getAnnotatedType().getType(), o));
            } else {
                if (args[0].required()) {
                    throw new InternalException("参数不存在: " + param.getName());
                } else {
                    params.add(null);
                }
            }
        }
        return params;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;
    }
}
